
-- our main need for this file is to track, within a given macro, exactly (or possibly in the case of cast/userandom) what spell or item is going to be used
-- next.  To do this, we need to parse each line of the given macro for certain things.
-- the ideal situation is when we have a #showtooltip and #show line with arguments, since this automatically makes life easy.

-- for the icon, determine if the #show [spell] line exists.  if so, then use the indicated spell.
-- next find out if either the #show line exists, or if the macro's icon is set to the ? mark icon.  if so we need to parse the rest.
-- otherwise simply return the icon of the macro chosen.  Embedded macros will use the override icon setting in InfiniBar.

-- for the tooltip, determine if the #showtooltip [spell] line exists.  if so, use the indicated spell as the tooltip.
-- next find out if the #showtooltip line exists.  if so we need to parse the rest.
-- otherwise simply set up the tooltip as WoW does.

local MAJOR_VERSION = "LibMacroParser-1.0"
local MINOR_VERSION = "$Revision: 49 $"

if not LibStub then error(MAJOR_VERSION .. " requires LibStub.") end
local lib = LibStub:NewLibrary(MAJOR_VERSION, MINOR_VERSION)
if not lib then return end

local type = type
local select = select
local GetMacroBody = GetMacroBody
local ipairs = ipairs
local strmatch = strmatch
local GetItemInfo = GetItemInfo
local GetSpellName = GetSpellName
local IsUsableItem = IsUsableItem
local IsUsableSpell = IsUsableSpell
local UnitExists = UnitExists
local SecureCmdOptionParse = SecureCmdOptionParse
local SecureCmdItemParse = SecureCmdItemParse
local QueryCastSequence = QueryCastSequence

lib.registered = lib.registered or {}
local registered = lib.registered
lib.slashList = lib.slashList or {}
local slashList = lib.slashList
lib.macrocache = lib.macrocache or {}
local macrocache = lib.macrocache

function lib:RegisterMacro(addon, callback, macro)
	if type(callback) ~= "string" and type(callback) ~= "function" then return end
	registered[macro] = registered[macro] or {}
	registered[macro][addon] = callback
end

function lib:IsMacroRegistered(addon, macro)
	return registered[macro] and registered[macro][addon]
end

function lib:UnregisterMacro(addon, macro)
	if registered[macro] and registered[macro][addon] then
		registered[macro][addon] = nil
		if not next(registered[macro]) then
			registered[macro] = nil
		end
	end
end

function lib:UnregisterAllMacros(addon)
	for macro, addons in pairs(registered) do
		lib:UnregisterMacro(addon, macro)
	end
end

local update = 0.2
local function OnUpdate(_, elapsed)
	update = update + elapsed
	if update < 0.2 then return end
	while update >= 0.2 do
		update = update - 0.2
	end

	for macro, addons in pairs(registered) do
		local oldType, oldData = lib:GetMacroInfo(macro)
		local mType, mData = lib:GetCurrentMacroInfo(macro)
		if mType ~= oldType or mData ~= oldData then
			for addon, callback in pairs(addons) do
				if type(callback) == "string" then
					addon[callback](addon, macro, mType, mData)
				elseif type(callback) == "function" then
					callback(addon, macro, mType, mData)
				end
			end
		end
	end
end

lib.frame = lib.frame or CreateFrame("Frame")
lib.frame:SetScript("OnUpdate", OnUpdate)

-- This may seem like a roundabout way to do this, but amazingly enough this reduces the per call memory usage of
--   GetEffectiveMacroInfo from 0.07 KiB to 0.0 KiB, except when the macro is first cached.  
function lib:MacroLines(macro)
	if not macrocache[macro] then
		local cache = {strsplit('\n', macro)}
		macrocache[macro] = cache
		for i = 1, #cache do
			local line = cache[i]
			local cmd, opts = strmatch(line, "(%S+)%s+(.*)")
			cmd = cmd and slashList[cmd:lower()] or false
			opts = opts or line
			cache[i] = { cmd, opts }
		end
	end
	return macrocache[macro]
end

do
	local _, lines, oldmacronum
	function lib:GetMacroInfo(macro)
		lines, oldmacronum = nil, nil
		if type(macro) == "number" then
			oldmacronum = macro
			macro = GetMacroBody(macro)
		end
		if not macro then return end
		if not registered[oldmacronum or macro] then
			return lib:GetCurrentMacroInfo(macro)
		end
		lines = lib:MacroLines(macro)
		return lines.mType or "macro", lines.mData or oldmacronum or macro
	end
end

do
	local mType, mData, nuType, nuData, oldmacronum
	function lib:GetCurrentMacroInfo(macro)
		mType, mData, nuType, nuData, oldmacronum = nil, nil, nil, nil, nil
		if type(macro) == "number" then
			oldmacronum = macro
			macro = GetMacroBody(macro)
		end
		if not macro then return end
		-- here we need to iterate through each line in the macro, and provide "none"; "spell", spellname; or "item", hyperlink
		local lines = lib:MacroLines(macro)
		lines.mType = nil
		lines.mData = nil
		for i,line in ipairs(lines) do
			local cmd,opts = line[1],line[2]
			if cmd then
				mType, mData = cmd(opts)
				if mType and mData then
					if (mType == "spell" and IsUsableSpell(mData)) or (mType == "item" and IsUsableItem(mData)) or (mType == "pet" and UnitExists("pet")) then
						lines.mType = mType
						lines.mData = mData
						return mType, mData
					elseif not nuType then
						nuType, nuData = mType, mData
					end
				end
			end
		end
		if nuType then
			return nuType, nuData
		end
		return 'macro', oldmacronum or macro
	end
end

function lib:RegisterSlashFunctions(slash,codeFunc)
	slashList[slash] = codeFunc
end

local action, target, iteminfo, iteminfo1, iteminfo2, _, actions, sequence, item, spell, bag, slot, items

lib:RegisterSlashFunctions("/startattack", function(msg) -- codeFunc
	action, target = SecureCmdOptionParse(msg)
	if action then return "spell", "Attack()" end
end)

local cast_func = function(msg) -- codeFunc
	action, target = SecureCmdOptionParse(msg)
	if action then
		action = strmatch(action,"^([^,]+)")
		local itemName = SecureCmdItemParse(action)
		local _, itemLink = GetItemInfo(itemName)
		if itemLink then
			return "item", itemLink
		elseif GetSpellName(action) then
			return "spell", action
		end
	end
end
lib:RegisterSlashFunctions("/cast", cast_func)
lib:RegisterSlashFunctions("/use", cast_func)
lib:RegisterSlashFunctions("#show", cast_func)
lib:RegisterSlashFunctions("#showtooltip", cast_func)
lib:RegisterSlashFunctions("/castrandom", cast_func)
lib:RegisterSlashFunctions("/userandom", cast_func)

local castsequence_func = function(msg) -- codeFunc
	sequence, target = SecureCmdOptionParse(msg)
	if sequence then
		_, item, spell = QueryCastSequence(sequence)
		if item then
			local _, itemName = GetItemInfo(item)
			return "item", itemName
		else
			return "spell", spell
		end
	end
end
lib:RegisterSlashFunctions("/castsequence", castsequence_func)
lib:RegisterSlashFunctions("/usesequence", castsequence_func)
lib:RegisterSlashFunctions("#showsequence", castsequence_func)

lib:RegisterSlashFunctions("/equip", function(msg) -- tooltipFunc
	item = SecureCmdOptionParse(msg)
	if item then
		item = SecureCmdItemParse(item)
		local _, itemName = GetItemInfo(item)
		return "item", itemName
	end
end)

lib:RegisterSlashFunctions("/equipslot", function(msg) -- tooltipFunc
	action = SecureCmdOptionParse(msg)
	if action then
		slot, item = strmatch(action, "^(%d+)%s*(.*)")
		if item then
			local _, itemName = GetItemInfo(item)
			return "item", itemName
		end
	end
end)

lib:RegisterSlashFunctions("/petattack", function(msg) -- tooltipFunc
	action, target = SecureCmdOptionParse(msg)
	if action then
		return "pet", 1
	end
end)

lib:RegisterSlashFunctions("/petfollow", function(msg) -- tooltipFunc
	action, target = SecureCmdOptionParse(msg)
	if action then
		return "pet", 2
	end
end)

lib:RegisterSlashFunctions("/petstay", function(msg) -- tooltipFunc
	action, target = SecureCmdOptionParse(msg)
	if action then
		return "pet", 3
	end
end)

lib:RegisterSlashFunctions("/petpassive", function(msg) -- tooltipFunc
	action, target = SecureCmdOptionParse(msg)
	if action then
		return "pet", 8
	end
end)

lib:RegisterSlashFunctions("/petdefensive", function(msg) -- tooltipFunc
	action, target = SecureCmdOptionParse(msg)
	if action then
		return "pet", 9
	end
end)

lib:RegisterSlashFunctions("/petaggressive", function(msg) -- tooltipFunc
	action, target = SecureCmdOptionParse(msg)
	if action then
		return "pet", 10
	end
end)